﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using BloxGin.Randomness;
using BloxGin.Blocks;
using System.Threading.Tasks;
using BloxGin.Cameras;
using System.Diagnostics;

namespace BloxGame
{
    public enum Directions : byte
    {
        Up, Down, Left, Right,None 
    }
    public class World
    {
        public const int SIZE = HeightMap.DefaultSize/2;//512
        public const byte HEIGHT = 128;
        public Block[] blocks = new Block[SIZE*HEIGHT*SIZE];
        public int Seed { get; protected set; }

        private const int BMX = SIZE * 128;
        private const int BMZ = 128;
        private GraphicsDevice device;
        private Chunk[,] chunks;
        private BasicEffect basicEffect;
        private ICamera camera;
        private Texture2D textureAtlas;
        private int xChunks;
        private int yChunks;

        public World(GraphicsDevice device,ICamera camera, Texture2D textureAtlas)
        {
            this.device = device;
            this.camera = camera;
            this.textureAtlas = textureAtlas;

            LoadContent();

        }

        private void InitBlocks()
        {
            for (int i = 0; i < blocks.Length; i++)
                blocks[i] = BlockTypes.None;
        }

        private void LoadContent()
        {
            InitBlocks();


            Map();
            
            basicEffect = new BasicEffect(device);
            basicEffect.Texture = textureAtlas;
            basicEffect.TextureEnabled = true;
            basicEffect.FogColor = Color.CornflowerBlue.ToVector3();

            
            basicEffect.FogEnd = 60;
            basicEffect.FogStart = basicEffect.FogEnd-40;
            camera.FarPlaneDistance = basicEffect.FogEnd;
            basicEffect.FogEnabled = true;

            basicEffect.Projection = camera.Projection;
            basicEffect.EnableDefaultLighting();  

            camera.Position = new Vector3(HeightMap.DefaultSize / 4, 50, HeightMap.DefaultSize / 4);
            
          
        }

        private void Map()
        {
            HeightMap map = GenerateHeightmap(0, 0);


            Parallel.For(0, map.Size, x =>
            {
                for (int z = 0; z < map.Size; z++)
                {
                    int y = (int)map[x, z];
                    if (y < 0)
                        y = 0;
                    if (y > map.Size - 1)
                        y = map.Size - 1;

                    BlockTypes blockt = BlockTypes.Dirt ;
                    if (y < 32)
                        blockt = BlockTypes.Rock;
                    SetBlock(x, y, z, blockt);
                    for (int y1 = y - 1; y1 > 0; y1--)
                    {
                        if (y1 < 32)
                            blockt = BlockTypes.Rock;
                        SetBlock(x, y1, z, blockt);
                    }
                }
            });

            xChunks = map.Size / Chunk.SIZE;
            yChunks = map.Size / Chunk.SIZE;
            chunks = new Chunk[xChunks, yChunks];

           // CarveTunnels(200);


            for(int x =0; x < xChunks;x++)
            {
                for (int y = 0; y < yChunks; y++)
                {
                    chunks[x, y] = new Chunk(device, this, x * Chunk.SIZE, y * Chunk.SIZE);
                    //chunks.Add( new Chunk(device, this, x * Chunk.SIZE, y * Chunk.SIZE));
                }
            }
        }

        private HeightMap GenerateHeightmap(int x, int y)
        {
            HeightMap map = new HeightMap(x, y);
            AlterHeightMap(ref map);
            
           
            return map;
        }

        private void AlterHeightMap(ref HeightMap map)
        {
            map.MakeFlat(5);
            map.SetNoise(40);
           
            map.Perturb(45f, 4);
            map.Erode(0.9f);

            map.Multiply(12);
            map.FlattenAreas(50);
            
        }

        private Chunk GetNewChunk(int x, int y)
        {
            HeightMap map = new HeightMap(x, y, Chunk.SIZE);
            Chunk c = new Chunk(device, this, x, y);
            return null;
        }


        public Block GetBlock(int x, int y, int z)
        {
            if (x < 0 || x >= SIZE || y < 0 || y >= HEIGHT || z < 0 || z >= SIZE)
                return BlockTypes.None;
            return blocks[x * BMX + z * BMZ + y];
        }

        public void SetBlock(int x, int y, int z, Block block)
        {
            if (x == 0 || x >= SIZE || y == 0 || y >= HEIGHT || z == 0 || z >= SIZE)
                return;
            blocks[x * BMX + z * BMZ + y] = block;

        }

        /// <summary>
        /// Enter the region's position in world coordinates to rebuild
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="z"></param>
        public void RebuildRegion(int x, int y, int z)
        {
           // Chunk c = chunks[x / Chunk.SIZE, z / Chunk.SIZE];
           // c.Rebuild(device, this);
        }

        /// <summary>
        /// Enter the blockPosition in WorldSpace
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="z"></param>
        public void AddSolid(int x, int y, int z,BlockTypes blockType)
        {
            SetBlock(x, y, z, blockType);
            Chunk c = chunks[x/Chunk.SIZE, z / Chunk.SIZE];
            byte xb,yb,zb;
            xb = (byte)(x % Chunk.SIZE);
            yb = (byte)(y);
            zb = (byte)(z % Chunk.SIZE);
            c.AddSolid(xb, yb, zb,device);
        }
        public void RemoveBlock(int x, int y, int z)
        {
            SetBlock(x, y, z, BlockTypes.None);
            Chunk c = chunks[x / xChunks, z / yChunks];
            byte xb, yb, zb;
            xb = (byte)(x % Chunk.SIZE);
            yb = (byte)(y);
            zb = (byte)(z % Chunk.SIZE);
           // c.AddSolid(xb, yb, zb, device);
            c.Rebuild(device, this);
        }

        /// <summary>
        /// Get chunk in world coordinates
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="z"></param>
        /// <returns></returns>
        public Chunk GetChunk(int x, int y, int z)
        {
            if (x >= SIZE || x <= 0 || z >= SIZE || z <= 0)
                return null;
            return chunks[x / Chunk.SIZE, z / Chunk.SIZE];
        }
        

        public void Draw(GameTime gameTime)
        {
            int draws = 0;

            foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
            {               
                basicEffect.View = camera.View;
               
                pass.Apply();

                for(int x = 0; x < xChunks; x++)
                    for (int y = 0; y < yChunks; y++)
                    {
                        Chunk c = chunks[x,y];
                        if (camera.BoundingFrustum.Contains(c.BoundingBox) != ContainmentType.Disjoint)
                        {
                            basicEffect.World = Matrix.Identity * Matrix.CreateTranslation(c.X, 0, c.Z);

                            device.SamplerStates[0] = SamplerState.PointClamp;
                            c.Draw(gameTime, device, basicEffect);
                            draws++;

                        }
                    }
             
            }
        }


        void WrapDown(object[] objects)
        {
            object numberFirst = objects[0];

            for (int i = 0; i < objects.Length - 1; i++)
            {
                objects[i] = objects[i + 1];
            }
            objects[objects.Length - 1] = numberFirst;

        }

        void WrapDown(List<Chunk> objects)
        {
            int numberFirstX = objects[0].X;
            int numberFirstY = objects[0].Z;

            for (int i = 0; i < objects.Count - 1; i++)
            {
                objects[i].X = objects[i + 1].Z;
                objects[i].X = objects[i + 1].Z;
            }
            objects[objects.Count - 1].X = numberFirstX;
            objects[objects.Count - 1].Z = numberFirstY;


            Block numberFirst = blocks[0];

            for (int i = 0; i < blocks.Length - 1; i++)
            {
                blocks[i] = blocks[i + 1];
            }
            blocks[objects.Count - 1] = numberFirst;
        }

        void WrapUp(object[] objects)
        {
            object numberLast = objects[objects.Length - 1];

            for (int i = objects.Length - 1; i > 0; i--)
            {
                objects[i] = objects[i - 1];
            }
            objects[0] = numberLast;

        }

        private void RegionLoader(Directions direction)
        {
            switch (direction)
            {
                case Directions.Up:

                    break;
                case Directions.Down:
                    break;
                case Directions.Left:
                    break;
                case Directions.Right:
                    break;
                default:
                    break;
            }
        }

        public bool SolidAtPoint(Vector3 position)
        {
            return SolidAt((int)position.X, (int)position.Y, (int)position.Z);
        }

        public bool SolidAt(int x, int y, int z)
        {

           BlockTypes blockType = GetBlock(x, y, z);
            return blockType != BlockTypes.None;
        }

        public BlockTypes BlockAtPoint(Vector3 position)
        {
            return GetBlock((int)position.X, (int)position.Y, (int)position.Z);
        }

        private void CarveTunnel(int count, int radius)
        {

            int sx = PerlinH.random.Next(SIZE);
            int sy = (int)(PerlinH.random.Next(HEIGHT) / 0.2f);
            int sz = PerlinH.random.Next(SIZE);

            int xdir = PerlinH.random.Next(2); if (xdir != 0) xdir = -1;
            int ydir = ydir = -1; 
           // r.Next(2); if (ydir == 0) ydir = -1;
            int zdir = PerlinH.random.Next(2); if (zdir == 0) zdir = -1;

            while (count > 0)
            {
                if (sx > radius + 5 && sy > radius + 5 && sz > radius + 5 &&
                    sx < SIZE - radius - 5 &&
                    sy < SIZE - radius - 5 &&
                    sz < SIZE - radius - 5)
                {
                    PaintAtPoint(sx, sy, sz, radius, BlockTypes.None);
                }

                sx += (PerlinH.random.Next(6) * xdir);
                sy += (PerlinH.random.Next(5) * ydir);
                sz += (PerlinH.random.Next(4) * zdir);

                radius += PerlinH.random.Next(12) - PerlinH.random.Next(2);

                if (radius < 1) radius = 2;
                if (radius > 8) radius = 3;

                count--;
            }
        }

        public void PaintAtPoint(int x, int y, int z, int paintRadius, BlockTypes paintValue)
        {
            for (int dx = -paintRadius; dx <= paintRadius; dx++)
                for (int dy = -paintRadius; dy <= paintRadius; dy++)
                    for (int dz = -paintRadius; dz <= paintRadius; dz++)
                        if (dx * dx + dy * dy + dz * dz < paintRadius * paintRadius)
                        {
                            //0Debug.WriteLine(string.Format("{0},{1},{2}", x + dx, y + dy, z + dz));
                            SetBlock(x + dx, y + dy, z + dz, paintValue);
                        }
        }

        public void Update(GameTime gameTime,Vector3 sunPosition)
        {

           /* _curChunk = GetChunk((int)camera.Position.X, 0, (int)camera.Position.Z);
            if(_prevChunk == null)
                _prevChunk = _curChunk;

            if(_prevChunk != _curChunk)
            {
                //Updayte

                Directions direction = Directions.None;

                int x = _curChunk.X - _prevChunk.X;
                int y = _curChunk.Z - _prevChunk.Z;

                if (x > 0)
                    direction = Directions.Left;
                else if (x < 0)
                    direction = Directions.Right;
                else if (y > 0)
                    direction = Directions.Up;
                else if (y < 0)
                    direction = Directions.Down;

                if (direction == Directions.None)
                    return;

                switch (direction)
                {
                    case Directions.Up:
                        for (int i = 0; i < Chunk.SIZE; i++)
                            WrapUp(blocks);

                       // chunks[0,0] = new Chunk(device,this,
                        break;
                    case Directions.Down:
                        break;
                    case Directions.Left:
                        break;
                    case Directions.Right:
                        break;
                    default:
                        break;
                }

            }

            _prevChunk = _curChunk;*/

        }



        private void CarveTunnels(int count)
        {

            for (int x = 0; x < count; x++)
            {
                CarveTunnel(1000, PerlinH.random.Next(10) + 4);
            }
        }
    }
}
